﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace GenericStateMachine
{
    /// <summary>
    /// Defines the base class of state.
    /// </summary>
    /// <typeparam name="TStateContext">The type of state context interface.</typeparam>
    /// <typeparam name="TState">The type of state interface.</typeparam>
    /// <typeparam name="TStateType">The type of concrete state enumeration.</typeparam>
    public abstract class StateBase<TStateContext, TState, TStateType>
        : IState<TStateContext, TState, TStateType>, IStateEx
        where TStateContext : IStateContext<TStateContext, TState, TStateType>
        where TState : IState<TStateContext, TState, TStateType>
        where TStateType : struct, IComparable, IFormattable, IConvertible
    {
        private static readonly ConcurrentDictionary<Type, TStateType> StateTypeCache = new ConcurrentDictionary<Type, TStateType>();

        private static TStateType GetStateType(Type stateClass)
        {
            var attr = stateClass.GetCustomAttributes<StateClassForAttribute>()
                                 .Single();

            var ret = (TStateType)attr.StateType;
            return ret;
        }

        private readonly TStateType stateType;

        protected StateBase()
        {
            this.stateType = StateTypeCache.GetOrAdd(GetType(), GetStateType);
        }

        /// <summary>
        /// Helper method to throw invalid state exception.
        /// </summary>
        /// <typeparam name="TReturn">The type of the return value of the method.</typeparam>
        /// <param name="expression">The method call expression.</param>
        protected virtual TReturn ThrowInvalidStateException<TReturn>(Expression<Func<TReturn>> expression)
        {
            var eventName = ExpressionHelper.GetMemberName(expression);
            var message = string.Format("{0}.{1} is invalid.", GetType().Name, eventName);
            throw new InvalidOperationException(message);
        }

        void IStateEx.SetContext(object context)
        {
            Context = (TStateContext)context;
        }

        public TStateContext Context
        {
            get; private set;
        }

        public TStateType StateType
        {
            get { return stateType; }
        }

        public virtual TStateType OnEntry()
        {
            // NOP
            return StateType;
        }

        public virtual void OnExit()
        {
            // NOP
        }

    }
}
